#pragma once

#include <functional>
#include <unordered_set>
#include <tsl/robin_set.h>
#include <zenox/generic_hash.hpp>
#include <typeindex>
#include <string>
#include <memory>
#include <any>

// fun fact: std::any is constructed null by default, null-any can be tested by any.has_value(), created by std::any()

namespace zenom {

enum class PortRole { // each represents a PortModel attribute
    PortName, // std::string
    PortDataType, // std::type_index
    PortInlineValue, // std::string
    PortVisible, // bool
};

struct NodeModel;

struct PortView { // GraphicsPort is a derived class of this
    virtual void on_update(PortRole role, std::any value) = 0; // slot, model to view
    virtual void on_link(NodeModel *node) = 0; // slot, model to view
    virtual void on_unlink(NodeModel *node) = 0; // slot, model to view
};

struct PortModel { // PortModel represents an input port of a node
    std::string name; // display name shown in ui as well as the internal port name (unique within a node)
    std::type_index data_type = typeid(void); // data type for the port value, created using std::type_index(typeid(T))
    std::string inline_value; // value expression specified from ui, only used when disconnected, not used when this port is linked to another node
    bool visible = true; // false to hide this port from node ui
    NodeModel *in_node{}; // where am I linked to? pointer to the node whose output port is connected to this input port, null if disconnected
    PortView *view{}; // pointer to the view object (in ui)

    void on_update(PortRole role, std::any value); // slot, view to model
    void on_link(NodeModel *node); // slot, view to model
    void on_unlink(); // slot, view to model

    void update(PortRole role, std::any value) { // public API to set attribute
        on_update(role, value);
        view->on_update(role, value);
    }

    void link(NodeModel *node) { // public API to set attribute
        on_link(node);
        view->on_link(node);
    }

    void unlink() { // public API to set attribute
        view->on_unlink(in_node);
        on_unlink();
    }
};

enum class NodeRole { // each represents a NodeModel attribute
    NodeType, // std::string
    NodeTitle, // std::string
    NodePortCount, // size_t
};

struct NodeView { // GraphicsNode is a derived class of this
    virtual void on_update(NodeRole role, std::any value) = 0; // slot, model to view
};

struct NodeModel { // NodeModel represents a node, it have 1-to-1 relationship with GraphicsItemNode
    std::string type; // actual node type (may not unique), should be immutable during the entire lifetime, e.g. "vdberode"
    std::string title; // node title shown in ui (unique within a graph), default value is type + <number>, e.g. "vdberode1"
    // hint: if another node with type "vdberode" inserted, it will be named as "vdberode2" for example
    std::vector<PortModel> ports; // input ports this node have, should not be resized after node created
    tsl::robin_set<PortModel *> out_ports{}; // enumerate all the links on the output port, empty if out port not connected
    NodeView *view{}; // pointer to the view object (in ui)

    void on_update(NodeRole role, std::any value); // slot, view to model

    void update(NodeRole role, std::any value) { // public API to set attribute
        on_update(role, value);
        view->on_update(role, value);
    }
};

enum class GraphRole { // each represents a GraphModel attribute
    GraphName, // std::string
};

struct GraphView { // GraphicsGraph is a derived class of this
    virtual void on_update(GraphRole role, std::any value) = 0; // slot, model to view
    virtual void on_new_node(NodeModel *node) = 0; // slot, model to view
    virtual void on_delete_node(NodeModel *node) = 0; // slot, model to view
};

struct GraphModel { // GraphModel represents a whole graph consists of several nodes
    std::string name; // graph name shown in ui (unique within the whole process)
    tsl::robin_set<std::unique_ptr<NodeModel>, zenox::pointer_hash, zenox::pointer_equal_to> nodes; // set of nodes this graph have
    /* tsl::robin_set<std::unique_ptr<LinkModel>, zenox::pointer_hash, zenox::pointer_equal_to> links; // set of links this graph have (NVM: link is already represented within port's link_node attr) */
    GraphView *view{}; // pointer to the view object (in ui)

    void on_update(GraphRole role, std::any value); // slot, view to model
    NodeModel *on_new_node(); // slot, view to model
    void on_delete_node(NodeModel *node); // slot, view to model

    void update(GraphRole role, std::any value) { // public API to set attribute
        on_update(role, value);
        view->on_update(role, value);
    }

    NodeModel *new_node() { // public API to set attribute
        auto node = on_new_node();
        view->on_new_node(node);
        return node;
    }

    void delete_node(NodeModel *node); // public API to set attribute
};

}
